import Vue from 'vue'

// tools
import debounce from 'throttle-debounce/debounce'

import {getColumnById, getRowIdentity, getTableQueryInfo } from './../unit/util.js'

/**
 *
 * @param array
 * @param rowKey
 * @returns {{}}
 */
const getKeysMap = function (array, rowKey) {
  const arrayMap = {};
  (array || []).forEach((row, index) => {
    arrayMap[getRowIdentity(row, rowKey)] = { row, index }
  })
  return arrayMap
}
/**
 *
 * @param states
 * @param row
 * @param selected
 * @returns {boolean}
 */
const toggleRowSelection = function (states, row, selected) {
  let changed = false
  const selection = states.selection
  const index = selection.indexOf(row)
  if (typeof selected === 'undefined') {
    if (index === -1) {
      selection.push(row)
      changed = true
    } else {
      selection.splice(index, 1)
      changed = true
    }
  } else {
    if (selected && index === -1) {
      selection.push(row)
      changed = true
    } else if (!selected && index > -1) {
      selection.splice(index, 1)
      changed = true
    }
  }

  return changed
}
/**
 *
 * @param states
 * @param row
 * @param expanded
 * @returns {boolean}
 */
const toggleRowExpansion = function (states, row, expanded) {
  let changed = false
  const expandRows = states.expandRows
  if (typeof expanded !== 'undefined') {
    const index = expandRows.indexOf(row)
    if (expanded) {
      if (index === -1) {
        expandRows.push(row)
        changed = true
      }
    } else {
      if (index !== -1) {
        expandRows.splice(index, 1)
        changed = true
      }
    }
  } else {
    const index = expandRows.indexOf(row)
    if (index === -1) {
      expandRows.push(row)
      changed = true
    } else {
      expandRows.splice(index, 1)
      changed = true
    }
  }
  return changed
}

const TableStore = function (table, initialState = {}) {
  if (!table) {
    throw new Error('Table is required.')
  }
  this.table = table

  this.states = {
    rowKey: null,
    _columns: [],
    originColumns: [],
    columns: [],
    fixedColumns: [],
    rightFixedColumns: [],
    leafColumns: [],
    fixedLeafColumns: [],
    rightFixedLeafColumns: [],
    leafColumnsLength: 0,
    fixedLeafColumnsLength: 0,
    rightFixedLeafColumnsLength: 0,
    isComplex: false,
    data: null,
    sortingColumn: null,
    sortProp: null,
    sortOrder: null,
    // 是否全部选中
    isAllSelected: false,
    // 选中的数据条目
    selection: [],
    reserveSelection: false,
    selectable: null,
    currentRow: null,
    hoverRow: null,
    expandRows: [],
    defaultExpandAll: false,
    selectOnIndeterminate: false,
    currentPageNo: 1,
    currentTotal: 0,
    isSplitPage: true,
    pageSize: 10,
    limit: 10,
    offset: 0
  }

  for (const prop in initialState) {
    if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
      this.states[prop] = initialState[prop]
    }
  }
}

TableStore.prototype.mutations = {
  setData (states, data) {
    const dataInstanceChanged = states._data !== data
    states._data = data
    states.data = data

    this.updateCurrentRow()

    if (!states.reserveSelection) {
      if (dataInstanceChanged) {
        this.clearSelection()
      } else {
        this.cleanSelection()
      }
      this.updateAllSelected()
    } else {
      const rowKey = states.rowKey
      if (rowKey) {
        const selection = states.selection
        const selectedMap = getKeysMap(selection, rowKey)

        states.data.forEach((row) => {
          const rowId = getRowIdentity(row, rowKey)
          const rowInfo = selectedMap[rowId]
          if (rowInfo) {
            selection[rowInfo.index] = row
          }
        })

        this.updateAllSelected()
      } else {
        console.warn('WARN: rowKey is required when reserve-selection is enabled.')
      }
    }

    const defaultExpandAll = states.defaultExpandAll
    if (defaultExpandAll) {
      this.states.expandRows = (states.data || []).slice(0)
    }

    Vue.nextTick(() => this.table.updateScrollY())
  },
  /**
   *
   * @param states
   * @param options
   * @description 改变排序的条件
   */
  changeSortCondition (states, options) {
    /*
     * 根据是否分页参数来判断是否触发load-data重新加载数据
     */
    if (this.table.isSplitPage) {
      this.table.offset = 0 // 上述方案导致table内的offset不正确
      const queryInfo = getTableQueryInfo(states, this.table.defaultSort, true)
      queryInfo.tableId = this.table.tableId
      this.table.$emit('load-data', queryInfo)
    }
    if (!options || !options.silent) {
      /**
       * #event
       *
       * @name    sort-change
       * @cn    排序脚本
       * @param   {column,prop,order} 列数据,属性,排序方向
       * @body console.log(1)
       * @description 当表格的排序条件发生变化的时候会触发该事件。
       */
      this.table.$emit('sort-change', {
        column: this.states.sortingColumn,
        prop: this.states.sortProp,
        order: this.states.sortOrder
      })

      /**
       * #event
       *
       * @name    load-data
       * @cn    排序脚本
       * @param  queryInfo 查询信息
       * @body  var _this = this;
       * Artery.loadPageData('data/rs1', queryInfo).then(function (result) {
       *   //_this.rs1 = result;
       * }).catch(function (error) {
       *   console.error(error);
       * });
       * @description 重新加载数据。
       */
      // this.table.$emit('load-data', getTableQueryInfo(states, this.table.defaultSort, true));
    }

    Vue.nextTick(() => this.table.updateScrollY())
  },

  filterChange_ (states) {
    const queryInfo = getTableQueryInfo(states, this.table.defaultSort, true)
    queryInfo.tableId = this.table.tableId
    this.table.$emit('load-data', queryInfo)
    this.table.offset = 0 // 因为要跳转第一页，这里会触发aty-pagination的监听更新页码  http://code.thunisoft.com/artery/artery6/issues/77
  },

  insertColumn (states, column, index, parent) {
    let array = states._columns
    if (parent) {
      array = parent.children
      if (!array) array = parent.children = []
    }

    if (typeof index !== 'undefined') {
      array.splice(index, 0, column)
    } else {
      array.push(column)
    }

    if (column.type === 'selection') {
      states.selectable = column.selectable
      states.reserveSelection = column.reserveSelection
    }

    if (this.table.$ready) {
      this.updateColumns() // hack for dynamics insert column
      this.scheduleLayout()
    }
  },

  updateHeader (states) {
    if (this.table.$ready) {
      this.updateColumns() // hack for dynamics insert column
      this.scheduleLayout()
    }
  },

  removeColumn (states, column, parent) {
    let array = states._columns
    if (parent) {
      array = parent.children
      if (!array) array = parent.children = []
    }
    if (array) {
      array.splice(array.indexOf(column), 1)
    }

    if (this.table.$ready) {
      this.updateColumns() // hack for dynamics remove column
      this.scheduleLayout()
    }
  },

  setHoverRow (states, row) {
    states.hoverRow = row
  },

  setCurrentRow (states, row) {
    const oldCurrentRow = states.currentRow
    states.currentRow = row

    if (oldCurrentRow !== row) {
      /**
       * #event
       *
       * @name    current-change
       * @cn       当前行变化事件
       * @param   row  当前新行
       * @param   oldCurrentRow  旧行
       * @description  当表格的当前行发生变化的时候会触发该事件，
       *如果要高亮当前行，请打开表格的 highlight-current-row 属性。
       * @body console.log(1)
       */
      this.table.$emit('current-change', row, oldCurrentRow)
    }
  },

  rowSelectedChanged (states, row) {
    const oldSelection = states.selection.slice()
    const changed = toggleRowSelection(states, row)
    const selection = states.selection

    if (changed) {
      const table = this.table
      /**
       * #event
       *
       * @name    selection-change
       * @cn      选中项发生变化事件
       * @param   newSelection 新选中的数据
       * @param   oldSelection 旧选中的数据
       * @param   changedSelection 变化的数据
       * @param   changedReason 变化的原因，取值为row-changed,clear,clean之一
       * @description  当选择项发生变化时会触发该事件。
       * @body console.log(1)
       */
      table.$emit('selection-change', selection ? selection.slice() : [], oldSelection, [row], 'row-changed')

      /**
       * #event
       *
       * @name    select
       * @cn      选中某一项事件
       * @param   selection 当前所有选中的数据
       * @param   row 当前操作的行数据
       * @description  当用户手动勾选数据行的 Checkbox 时触发的事件。
       * @body console.log(1)
       */
      table.$emit('select', selection, row)
    }

    this.updateAllSelected()
  },
  setCurrentPageNo (states, current) {
    states.currentPageNo = current
  },
  setCurrentTotal (states, total) {
    states.currentTotal = total
  },
  setLimit (states, limit) {
    states.limit = limit
  },
  setOffset (states, offset) {
    states.offset = offset
  },
  toggleAllSelection: debounce(10, function (states) {
    const data = states.data || []
    if (data.length === 0) return
    const oldSelection = states.selection.slice()
    const selection = this.states.selection
    /*
     * when only some rows are selected (but not all), select or deselect all of them
     * depending on the value of selectOnIndeterminate
     */
    const value = states.selectOnIndeterminate
      ? !states.isAllSelected
      : !(states.isAllSelected || selection.length)
    let selectionChanged = false

    data.forEach((item, index) => {
      if (states.selectable) {
        if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
          selectionChanged = true
        }
      } else {
        if (toggleRowSelection(states, item, value)) {
          selectionChanged = true
        }
      }
    })

    const table = this.table
    if (selectionChanged) {
      table.$emit('selection-change', selection ? selection.slice() : [], oldSelection, data, 'row-changed')
    }
    /**
     * #event
     *
     * @name    select-all
     * @cn      点击全选事件
     * @param   selection 当前所有选中的数据
     * @description  当用户手动勾选全选 Checkbox 时触发的事件。
     * @body console.log(1)
     */
    table.$emit('select-all', selection)
    states.isAllSelected = value
  }
  )
}

const doFlattenColumns = (columns) => {
  const result = []
  columns.forEach((column) => {
    if (column.children) {
      result.push.apply(result, doFlattenColumns(column.children))
    } else {
      result.push(column)
    }
  })
  return result
}

TableStore.prototype.updateColumns = function () {
  const states = this.states
  // 排除数据列
  const _columns = (states._columns || []).filter((column) => column.type != 'data')
  states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left')
  states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right')

  if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
    _columns[0].fixed = true
    states.fixedColumns.unshift(_columns[0])
  }

  const notFixedColumns = _columns.filter(column => !column.fixed)
  states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns)

  const leafColumns = doFlattenColumns(notFixedColumns)
  const fixedLeafColumns = doFlattenColumns(states.fixedColumns)
  const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns)

  states.leafColumnsLength = leafColumns.length
  states.fixedLeafColumnsLength = fixedLeafColumns.length
  states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length

  states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns)
  states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0
}

TableStore.prototype.isCurrent = function (row) {
  return this.states.currentRow === row
}

TableStore.prototype.isSelected = function (row) {
  return (this.states.selection || []).indexOf(row) > -1
}

TableStore.prototype.clearSelection = function () {
  const states = this.states
  states.isAllSelected = false
  const oldSelection = states.selection
  if (states.selection.length) {
    states.selection = []
  }
  if (oldSelection.length > 0) {
    this.table.$emit('selection-change', states.selection ? states.selection.slice() : [], oldSelection, oldSelection, 'clear')
  }
}

TableStore.prototype.toggleRowSelectionByKey = function (key, selected) {
  const states = this.states
  const rowKey = states.rowKey
  if (!rowKey) throw new Error('[Table] row-key should not be empty.')
  const data = states.data || []
  const keysMap = getKeysMap(data, rowKey)
  const info = keysMap[key]
  if (info) {
    this.toggleRowSelection(info.row, selected)
  }
}

TableStore.prototype.toggleRowSelection = function (row, selected) {
  const oldSelection = this.states.selection.slice()
  const changed = toggleRowSelection(this.states, row, selected)
  if (changed) {
    this.table.$emit('selection-change', this.states.selection ? this.states.selection.slice() : [], oldSelection, row, 'row-changed')
  }
}

TableStore.prototype.toggleRowExpansionByKey = function (key, expanded) {
  const states = this.states
  const rowKey = states.rowKey
  if (!rowKey) throw new Error('[Table] row-key should not be empty.')
  const data = states.data || []
  const keysMap = getKeysMap(data, rowKey)
  const info = keysMap[key]
  if (info) {
    this.toggleRowExpansion(info.row, expanded)
  }
}

TableStore.prototype.toggleRowExpansion = function (row, expanded) {
  const changed = toggleRowExpansion(this.states, row, expanded)
  if (changed) {
    /**
     * #event
     *
     * @name    expand-change
     * @cn      展开收起事件
     * @param   row 当前操作的行数据
     * @param   expandRows 当前所有展开行的数据
     * @description  当用户对某一行展开或者关闭的时候会触发该事件。
     * @body console.log(1)
     */
    this.table.$emit('expand-change', row, this.states.expandRows)
    this.scheduleLayout()
  }
}

TableStore.prototype.collapseExpansion = function () {
  const states = this.states
  const oldExpandRows = states.expandRows
  if (states.expandRows.length) {
    states.expandRows = []
  }
  if (oldExpandRows.length > 0) {
    this.table.$emit('expand-change', oldExpandRows, [])
  }
}

TableStore.prototype.isRowExpanded = function (row) {
  const { expandRows = [], rowKey } = this.states
  if (rowKey) {
    const expandMap = getKeysMap(expandRows, rowKey)
    return !!expandMap[getRowIdentity(row, rowKey)]
  }
  return expandRows.indexOf(row) !== -1
}

TableStore.prototype.cleanSelection = function () {
  const oldSelection = this.states.selection.slice()
  const selection = this.states.selection || []
  const data = this.states.data
  const rowKey = this.states.rowKey
  let deleted
  if (rowKey) {
    deleted = []
    const selectedMap = getKeysMap(selection, rowKey)
    const dataMap = getKeysMap(data, rowKey)
    for (const key in selectedMap) {
      if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
        deleted.push(selectedMap[key].row)
      }
    }
  } else {
    deleted = selection.filter((item) => {
      return data.indexOf(item) === -1
    })
  }

  const changedSelection = []
  deleted.forEach((deletedItem) => {
    const deletedRow = selection.indexOf(deletedItem)
    selection.splice(deletedRow, 1)
    changedSelection.push(deletedRow)
  })

  if (deleted.length) {
    this.table.$emit('selection-change', selection ? selection.slice() : [], oldSelection, changedSelection, 'clean')
  }
}

TableStore.prototype.clearSort = function () {
  const states = this.states
  if (!states.sortingColumn) return
  states.sortingColumn.order = null
  states.sortProp = null
  states.sortOrder = null

  this.commit('changeSortCondition', {
    silent: true
  })
}

TableStore.prototype.updateAllSelected = function () {
  const states = this.states
  const { selection, rowKey, selectable, data } = states
  if (!data || data.length === 0) {
    states.isAllSelected = false
    return
  }

  let selectedMap
  if (rowKey) {
    selectedMap = getKeysMap(states.selection, rowKey)
  }

  const isSelected = function (row) {
    if (selectedMap) {
      return !!selectedMap[getRowIdentity(row, rowKey)]
    } else {
      return selection.indexOf(row) !== -1
    }
  }

  let isAllSelected = true
  let selectedCount = 0
  for (let i = 0, j = data.length; i < j; i++) {
    const item = data[i]
    const isRowSelectable = selectable && selectable.call(null, item, i)
    if (!isSelected(item)) {
      if (!selectable || isRowSelectable) {
        isAllSelected = false
        break
      }
    } else {
      selectedCount++
    }
  }

  if (selectedCount === 0) isAllSelected = false

  states.isAllSelected = isAllSelected
}

TableStore.prototype.scheduleLayout = function (updateColumns) {
  if (updateColumns) {
    this.updateColumns()
  }
  this.table.debouncedUpdateLayout()
}

TableStore.prototype.setCurrentRowByKey = function (key) {
  const states = this.states
  const rowKey = states.rowKey
  if (!rowKey) throw new Error('[Table] row-key should not be empty.')
  const data = states.data || []
  const keysMap = getKeysMap(data, rowKey)
  const info = keysMap[key]
  if (info) {
    this.commit('setCurrentRow', info.row)
  } else {
    this.commit('setCurrentRow', null)
  }
}

TableStore.prototype.updateCurrentRow = function () {
  const states = this.states
  const table = this.table
  const data = states.data || []
  const oldCurrentRow = states.currentRow

  if (data.indexOf(oldCurrentRow) === -1) {
    states.currentRow = null

    if (states.currentRow !== oldCurrentRow) {
      table.$emit('current-change', null, oldCurrentRow)
    }
  }
}

TableStore.prototype.commit = function (name, ...args) {
  const mutations = this.mutations
  if (mutations[name]) {
    mutations[name].apply(this, [this.states].concat(args))
  } else {
    throw new Error(`Action not found: ${name}`)
  }
}

export default TableStore
